1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 module hip.util.reflection; 12 int asInt(alias enumMember)() 13 { 14 alias T = typeof(enumMember); 15 foreach(i, mem; __traits(allMembers, T)) 16 { 17 if(mem == enumMember.stringof) 18 return i; 19 } 20 ErrorHandler.assertLazyExit(0, "Member "~enumMember.stringof~" from type " ~ T.stringof~ " does not exist"); 21 } 22 23 /** 24 * Used when wanting to represent any struct compatible with a static array. 25 */ 26 bool isTypeArrayOf(Type, Array, int Count)() 27 { 28 static if(is(Array == Type[Count])) 29 return true; 30 else static if(is(Array T : T*)) 31 return false; 32 else 33 { 34 int count = 0; 35 static foreach(v; Array.tupleof) 36 static if(is(typeof(v) == Type)) 37 count++; 38 else static if(__traits(isStaticArray, typeof(v)) && is(typeof(v.init[0]) == Type)) 39 count+= v.length; 40 return count == Count; 41 } 42 } 43 44 bool isLiteral(alias variable)(string var = variable.stringof) 45 { 46 import hip.util.string : isNumeric; 47 return (isNumeric(var) || (var[0] == '"' && var[$-1] == '"')); 48 } 49 50 51 ///Copy pasted from std.traits for not importing too many things 52 template isFunction(X...) 53 if (X.length == 1) 54 { 55 static if (is(typeof(&X[0]) U : U*) && is(U == function) || 56 is(typeof(&X[0]) U == delegate)) 57 { 58 // x is a (nested) function symbol. 59 enum isFunction = true; 60 } 61 else static if (is(X[0] T)) 62 { 63 // x is a type. Take the type of it and examine. 64 enum isFunction = is(T == function); 65 } 66 else 67 enum isFunction = false; 68 } 69 70 template getParams (alias fn) 71 { 72 static if ( is(typeof(fn) params == __parameters) ) 73 alias getParams = params; 74 } 75 alias Parameters = getParams; 76 enum hasModule(string modulePath) = (is(typeof((){mixin("import ", modulePath, ";");}))); 77 enum hasType(string TypeName) = __traits(compiles, {mixin(TypeName, " _;");}); 78 79 template isEnum(alias s) 80 { 81 static if(is(s == enum)) 82 enum bool isEnum = true; 83 else 84 enum bool isEnum = false; 85 } 86 87 88 template getUDAs(alias symbol) 89 { 90 enum getUDAs = __traits(getAttributes, symbol); 91 } 92 93 template hasUDA(alias symbol, alias UDA) 94 { 95 enum helper = () 96 { 97 bool ret = false; 98 foreach(att; __traits(getAttributes, symbol)) 99 { 100 static if(is(typeof(UDA))) 101 { 102 if(att == UDA) ret = true; 103 } 104 else 105 { 106 if(is(typeof(att) == UDA) || is(att == UDA)) ret = true; 107 } 108 } 109 return ret; 110 }(); 111 enum hasUDA = helper; 112 } 113 template isReference(T) 114 { 115 enum isReference = is(T == class) || is(T == interface); 116 } 117 template hasMethod(T, string method, Params...) 118 { 119 enum hasMethod = __traits(hasMember, T, method) && is(getParams!(__traits(getMember, T, method)) == Params); 120 } 121 122 auto inverseLookup(alias lookupTable)() 123 { 124 alias O = typeof(lookupTable.keys[0]); 125 alias I = typeof(lookupTable.values[0]); 126 O[I] output; 127 static foreach(k, v; lookupTable) 128 output[v] = k; 129 return output; 130 } 131 132 /** 133 * Generates a function that executes a switch case from the associative array. 134 * 135 * Pass true as the second parameter if reverse is desired. 136 */ 137 template aaToSwitch(alias aa, bool reverse = false) 138 { 139 auto aaToSwitch(T)(T value) 140 { 141 switch(value) 142 { 143 static foreach(k, v; aa) 144 static if(reverse) 145 case v: return k; 146 else 147 case k: return v; 148 default: 149 return typeof(return).init; 150 } 151 } 152 } 153 154 155 size_t enumLength(T)() 156 if(is(T == enum)) 157 { 158 return __traits(allMembers, T).length; 159 } 160 161 T nullCheck(string expression, T, Q)(T defaultValue, Q target) 162 { 163 import std.traits:ReturnType; 164 import hip.util.conv:to; 165 import hip.util.string:split; 166 167 enum exps = expression.split("."); 168 enum firstExp = exps[0]~"."~exps[1]; 169 170 mixin("alias ",exps[0]," = target;"); 171 172 if(target is null) 173 return defaultValue; 174 175 static if(mixin("isFunction!("~firstExp~")")) 176 { 177 mixin("ReturnType!("~firstExp~") _v0 = "~firstExp~";"); 178 if(_v0 is null) return defaultValue; 179 } 180 else 181 mixin("typeof("~firstExp~") _v0 = " ~ firstExp~";"); 182 183 static foreach(i, e; exps[2..$]) 184 { 185 static if(mixin("isFunction!(_v"~to!string(i)~"."~e~")")) 186 { 187 mixin("ReturnType!(_v"~to!string(i)~"."~e~") _v"~to!string(i+1) ~"= _v"~to!string(i)~"."~e~";"); 188 } 189 else 190 { 191 mixin("typeof(_v"~to!string(i)~"."~e~") _v"~to!string(i+1)~"= _v"~to!string(i)~"."~e~";"); 192 } 193 static if(i != exps[2..$].length - 1) 194 mixin("if (_v"~to!string(i+1)~" is null) return defaultValue;"); 195 } 196 197 mixin("return _v",to!string(exps.length-2),";"); 198 } 199 200 201 /** 202 * Used on: 203 * - Static Methods 204 * - Class Names 205 * Used in conjunction to ExportDFunctions. 206 * You may specify a suffix, if you so, `_suffix` is added 207 * ExportD will do nothing to static methods when building release. However, it will still produce 208 * a function for returning a new class. 209 */ 210 struct ExportD{string suffix;} 211 212 213 /** 214 * Will basically generate an export name such as className_funcSymbol 215 * If it has ExportD with a suffix, it will be basically className_funcSymbol(suffix) 216 */ 217 template generateExportName(string className, alias funcSymbol) 218 { 219 //Means that it has a suffix 220 static if(is(typeof(__traits(getAttributes, funcSymbol)[0]) == ExportD)) 221 enum generateExportName = className~"_"~__traits(identifier, funcSymbol)~"_"~__traits(getAttributes, funcSymbol)[0].suffix; 222 else 223 enum generateExportName = className~"_"~__traits(identifier, funcSymbol); 224 } 225 226 /** 227 * Returns a code to be mixed in. 228 * If isRef, it will call with hipSaveRef for not being colled 229 * funcCallCode can be anything as `className.functionName` or even `new Class` 230 */ 231 private string getExportedFuncImpl(bool isRef, string funcCallCode) 232 { 233 string ret; 234 if(isRef) 235 { 236 ret = q{ 237 import hip.util.lifetime; 238 return cast(typeof(return))hipSaveRef(cast(Object) 239 } ~ funcCallCode~"(__traits(parameters)));}"; 240 } 241 else 242 { 243 ret = "return "~funcCallCode~"(__traits(parameters));}"; 244 } 245 return ret; 246 } 247 248 249 ///ClassT, Ctor, string className 250 ///This class MUST have an interface, because it will bug out when calling the function with `need opCmp for class` 251 template generateExportConstructor(ClassT, string className) 252 { 253 enum impl = () 254 { 255 return "export extern(System) I" ~ className ~ " new" ~ className ~ //export extern(System) Class newClass 256 "(getParams!(__traits(getMember, Class, \"__ctor\"))){"~ //(A a, B b...) 257 getExportedFuncImpl 258 ( 259 true, //IsReference 260 "new "~className 261 ); 262 }(); 263 264 enum generateExportConstructor = impl; 265 } 266 267 /** 268 * It will create a `export extern(System)` function, thus, making it a C callable code. 269 * This function comes from a static method, and has special code injection for making the 270 * GC not collect if it is an object 271 */ 272 template generateExportFunc(string className, alias funcSymbol) 273 { 274 import std.traits:ReturnType; 275 enum impl = () 276 { 277 alias RetType = ReturnType!funcSymbol; 278 string ret = "export extern(System) "~RetType.stringof~" "~generateExportName!(className, funcSymbol); 279 ret~= "(getParams!(sym)){"; 280 281 ret~= getExportedFuncImpl 282 ( 283 isReference!(RetType), 284 className~"."~__traits(identifier, funcSymbol) 285 ); 286 287 return ret; 288 }(); 289 290 enum generateExportFunc = impl; 291 } 292 293 294 struct Version 295 { 296 template opDispatch(string s) 297 { 298 mixin(`version(`~s~`)enum opDispatch=true;else enum opDispatch=false;`); 299 } 300 } 301 302 303 304 ///Intermediary step for getting an alias to the Class type 305 mixin template ExportDFunctionsImpl(string className, Class) 306 { 307 //If the class has ExportD, it will export a function called new(ClassName) 308 //It can't contain more than one constructor. 309 static if(hasUDA!(Class, ExportD)) 310 { 311 static assert( 312 __traits(getOverloads, Class, "__ctor").length == 1, 313 "Can't export class with more than one constructor ("~className~")" 314 ); 315 mixin( 316 generateExportConstructor!(Class, className) 317 ); 318 pragma(msg, "Exported Class "~className); 319 } 320 //Get all static methods that has ExportD 321 static foreach(sym; getSymbolsByUDA!(Class, ExportD)) 322 { 323 static if(!is(sym == Class)) 324 { 325 //Assert that the symbol to generate does not exists yet 326 static assert(__traits(compiles, mixin(generateExportName!(className, sym))), 327 "ExportD '" ~ generateExportName!(className, sym) ~ 328 "' is not unique, use ExportD(\"SomeName\") for overloading with a suffix"); 329 330 pragma(msg, "Exported "~(generateExportName!(className, sym))); 331 //Func signature 332 //Check if it is a non value type 333 mixin(generateExportFunc!(className, sym)); 334 } 335 } 336 } 337 338 339 /** 340 * Iterates through a module and generates `export` function declaration for each 341 * @ExportD function found on it. 342 * If the class itself is @ExportD, it will create a method new(ClassName) to be exported too 343 */ 344 mixin template ExportDFunctions(alias mod) 345 { 346 import std.traits:getSymbolsByUDA, ParameterIdentifierTuple; 347 pragma(msg, "Exporting ", mod.stringof); 348 static foreach(mem; __traits(allMembers, mod)) 349 { 350 //Currently only supported on classes and structs 351 static if( (is(__traits(getMember, mod, member) == class) || is(__traits(getMember, mod, member) == struct) )) 352 { 353 mixin ExportDFunctionsImpl!(mem, __traits(getMember, mod, member)); 354 } 355 } 356 } 357 358 359 /** 360 * Intermediary step for getting an alias to the Class type 361 * The difference with HipExportDFunctionsImpl is that it does not generate 362 * Static method output when not in script version. 363 */ 364 mixin template HipExportDFunctionsImpl(string className, Class) 365 { 366 //If the class has ExportD, it will export a function called new(ClassName) 367 //It can't contain more than one constructor. 368 static if(hasUDA!(Class, ExportD)) 369 { 370 static assert( 371 __traits(getOverloads, Class, "__ctor").length == 1, 372 "Can't export class with more than one constructor ("~className~")" 373 ); 374 mixin( 375 generateExportConstructor!(Class, className) 376 ); 377 pragma(msg, "Exported Class "~className); 378 } 379 version(Load_DScript) 380 { 381 //Get all static methods that has ExportD 382 static foreach(sym; getSymbolsByUDA!(Class, ExportD)) 383 { 384 static if(!is(sym == Class)) 385 { 386 //Assert that the symbol to generate does not exists yet 387 static assert(__traits(compiles, mixin(generateExportName!(className, sym))), 388 "ExportD '" ~ generateExportName!(className, sym) ~ 389 "' is not unique, use ExportD(\"SomeName\") for overloading with a suffix"); 390 391 pragma(msg, "Exported "~(generateExportName!(className, sym))); 392 //Func signature 393 //Check if it is a non value type 394 mixin(generateExportFunc!(className, sym)); 395 } 396 } 397 } 398 } 399 400 /** 401 * Iterates through a module and generates `export` function declaration for each 402 * @ExportD function found on it. 403 * If the class itself is @ExportD, it will create a method new(ClassName) to be exported too 404 * * The difference with HipExportDFunctions is that it does not generate 405 * * Static method output when not in script version. 406 */ 407 mixin template HipExportDFunctions(alias mod) 408 { 409 import std.traits:getSymbolsByUDA; 410 pragma(msg, "Exporting ", mod.stringof); 411 static foreach(mem; __traits(allMembers, mod)) 412 { 413 //Currently only supported on classes and structs 414 static if( (is(__traits(getMember, mod, mem) == class) || is(__traits(getMember, mod, mem) == struct) )) 415 { 416 mixin HipExportDFunctionsImpl!(mem, __traits(getMember, mod, mem)); 417 } 418 } 419 } 420 421 422 string attributes(alias member)() 423 { 424 string ret; 425 foreach(attr; __traits(getFunctionAttributes, member)) 426 ret~= attr ~ " "; 427 return ret; 428 } 429 430 431 432 433 template hasOverload(T,string member, OverloadType) 434 { 435 bool impl() 436 { 437 bool ret = false; 438 static foreach(ov; __traits(getVirtualMethods, T, member)) 439 static if(is(typeof(ov) == OverloadType)) 440 ret = true; 441 return ret; 442 } 443 444 enum hasOverload = impl; 445 } 446 447 448 bool isMethodImplemented(T, string member, FuncType)() 449 { 450 bool ret; 451 static foreach(overload; __traits(getVirtualMethods, T, member)) 452 if(is(typeof(overload) == FuncType) && !__traits(isAbstractFunction, overload)) 453 ret = true; 454 return ret; 455 } 456 457 458 /** 459 * Private to forward interface 460 */ 461 string ForwardFunc(alias func, string funcName, string member)() 462 { 463 return attributes!func~ " ReturnType!(ov) " ~ funcName ~ "(getParams!(ov))"~ 464 "{ return " ~ member ~ "." ~funcName ~ "(__traits(parameters));}"; 465 } 466 467 /** 468 * This function receives a string containing the member name which implements the interface I. 469 * 470 * So, whenever something calls the interface.memberFunction, it will forward the call to that member by doing 471 * `void memberFunction(){member.memberFunction();}`, if the function is already defined, it will be ignored. 472 * 473 * [Dev: Futurely, it should be changed to use `alias member` instead of getting its string.] 474 */ 475 string ForwardInterface(string member, I)() if(is(I == interface)) 476 { 477 import hip.util.string:replaceAll; 478 479 return q{ 480 import std.traits:ReturnType; 481 import hip.util.reflection:isMethodImplemented, ForwardFunc, getParams; 482 483 static assert(is(typeof($member) : $I), 484 "For forwarding the interface, the member $member should be or implement $I" 485 ); 486 487 static foreach(m; __traits(allMembers, $I)) 488 static foreach(ov; __traits(getVirtualMethods, $I, m)) 489 { 490 //Check for overloads here 491 static if(!isMethodImplemented!(typeof(this), m, typeof(ov))) 492 mixin(ForwardFunc!(ov, m, "$member")); 493 } 494 }.replaceAll("$I", I.stringof).replaceAll("$member", member); 495 } 496 497 mixin template ForwardInterface2(string member, I) if(is(I == interface)) 498 { 499 import hip.util.reflection:isMethodImplemented, ForwardFunc; 500 501 static assert(is(typeof(mixin(member)) : I), 502 "For forwarding the interface, the member "~member~" should be or implement "~I.stringof 503 ); 504 505 static foreach(m; __traits(allMembers, I)) 506 static foreach(ov; __traits(getVirtualMethods, I, m)) 507 { 508 //Check for overloads here 509 static if(!isMethodImplemented!(typeof(this), m, typeof(ov))) 510 mixin(ForwardFunc!(ov, m, member)); 511 } 512 } 513 514 mixin template MultiInherit(I) if(is(I == interface)) 515 { 516 mixin("private I ",I.stringof,"_impl"); 517 mixin(ForwardInterface!(I.stringof~"_impl", I)); 518 } 519 520 521 void GenerateGetterSettersInterfaceImpl(interface_)() 522 { 523 import hip.util.array; 524 foreach(mem; __traits(allMembers, interface_)) 525 { 526 alias member = __traits(getMember, interface_, mem); 527 if(__traits(isFinalFunction, member)) 528 continue; 529 auto attributes = __traits(getFunctionAttributes, member); 530 if(attributes.contains("ref")) 531 continue; 532 } 533 534 } 535 536 /** 537 * Generates getter and setter for given interface. 538 * - Final methods are excluded. 539 * - `void` return types are excluded 540 * - `const` methods will only generate the const getter 541 */ 542 mixin template GenerateGettersSettersInterface(interface_) if(is(interface_ == interface)) 543 { 544 static foreach(mem; __traits(allMembers, interface_)) 545 { 546 547 } 548 } 549 550 /** 551 * This mixin is able to generate runtime accessors. That means that by having a string, it is 552 *possible to modify 553 */ 554 mixin template GenerateRuntimeAccessors() 555 { 556 T* getProperty(T)(string prop) 557 { 558 alias T_this = typeof(this); 559 560 switch(prop) 561 { 562 static foreach(member; __traits(allMembers, T_this)) 563 { 564 static if(is(typeof(__traits(getMember, T_this, member)) == T)) 565 { 566 case member: 567 return &__traits(getMember, T_this, member); 568 } 569 } 570 default: 571 return null; 572 } 573 } 574 575 void setProperty(T)(string propName, T value) 576 { 577 T* prop = getProperty!T(propName); 578 if(prop !is null) 579 *prop = value; 580 } 581 }